package com.abel.bigwater.wflow

import com.abel.bigmeter.service.MeterParam
import com.abel.bigwater.model.*
import com.abel.bigwater.model.ticket.MeterHistoryPulse
import com.abel.bigwater.model.ticket.SetupMeter
import com.abel.bigwater.model.zone.ZoneMeter
import com.abel.bigwater.wflow.mapper.ConfigMapper
import com.abel.bigwater.wflow.mapper.LocMapper
import com.abel.bigwater.wflow.mapper.MeterMapper
import com.abel.bigwater.wflow.mapper.TicketMapper
import org.activiti.api.process.model.ProcessDefinition
import org.activiti.api.process.model.ProcessInstance
import org.activiti.api.process.model.builders.DeleteProcessPayloadBuilder
import org.activiti.api.process.model.builders.GetProcessDefinitionsPayloadBuilder
import org.activiti.api.process.model.builders.StartProcessPayloadBuilder
import org.activiti.api.process.runtime.ProcessRuntime
import org.activiti.api.runtime.shared.query.Pageable
import org.activiti.api.task.model.Task
import org.activiti.api.task.model.builders.CompleteTaskPayloadBuilder
import org.activiti.api.task.model.builders.GetTasksPayloadBuilder
import org.activiti.api.task.model.payloads.GetTaskVariablesPayload
import org.activiti.api.task.runtime.TaskRuntime
import org.activiti.engine.ProcessEngine
import org.activiti.engine.impl.identity.Authentication
import org.joda.time.DateTime
import org.joda.time.format.ISODateTimeFormat
import org.locationtech.jts.geom.Geometry
import org.locationtech.jts.geom.GeometryCollection
import org.locationtech.jts.geom.Point
import org.locationtech.jts.io.WKTReader
import org.locationtech.jts.io.WKTWriter
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.ui.set
import org.springframework.web.bind.annotation.*
import java.util.*
import javax.servlet.http.HttpServletRequest
import kotlin.collections.ArrayList

@Controller
@RequestMapping("/setupMeter")
class SetupMeterController {
    @Autowired
    var meterMapper: MeterMapper? = null

    @Autowired
    var locMapper: LocMapper? = null

    @Autowired
    var configMapper: ConfigMapper? = null

    @Autowired
    var ticketMapper: TicketMapper? = null

    @Autowired
    var processRuntime: ProcessRuntime? = null

    @Autowired
    var taskRuntime: TaskRuntime? = null

    @Autowired
    var processEngine: ProcessEngine? = null

    /**
     * 启动流程
     */
    @RequestMapping("/startSetup/{pdId}")
    fun startSetup(m: Model, @PathVariable("pdId") pdId: String): String {
        val me = DemoApplicationConfiguration.myself()
        me ?: return URL_LOGIN

        var pd: ProcessDefinition? = null
        try {
            pd = processRuntime?.processDefinition(pdId)

            if (pd?.key != PROC_SETUP_METER) {
                m.set(ERRORS_KEY, listOf("未发现对应的流程模板: ${pdId}."))
                return ERROR_LIST
            }

            m.set("pd", pd)
            m.set("meterList", meterMapper!!.selectMeter(MeterParam(firmId = me.firmId)))
            m.set("firmList", configMapper!!.selectFirm(firmId = me.firmId + "%"))

            return "meter-list-install"
        } catch (ex: Exception) {
            lgr.error("fail to fetch proc-def with ${pdId} caused by ${ex.message}")
            m.set(ERRORS_KEY, listOf("发起流程 ${pdId} 故障: ${ex.message}"))
            return ERROR_LIST
        }
    }

    /**
     * 签收任务，根据任务名称定向到不同的视图
     */
    @RequestMapping("/claimTask/{taskId}")
    fun claimTask(m: Model, @PathVariable("taskId") taskId: String): String {
        val me = DemoApplicationConfiguration.myself()
        me ?: return URL_LOGIN

        var task: Task?
        try {
            task = taskRuntime?.task(taskId)
        } catch (ex: Exception) {
            lgr.error("fail to fetch task ${taskId} caused by ${ex.message}")
            m.set("errors", listOf("任务已完成(或不存在): ${taskId}"))
            return RM_TASK_LIST
        }

        val pinst = processRuntime?.processInstance(task?.processInstanceId)
        lgr.info("工单KEY: ${pinst?.businessKey}")

        if (task == null || pinst == null) {
            m.set("errors", listOf("流程或任务ID不存在：${taskId}"))

            return ERROR_LIST
        } else if (task.status != Task.TaskStatus.CREATED && task.status != Task.TaskStatus.ASSIGNED) {
            m.set("errors", listOf("任务无法再次分配"))

            return ERROR_LIST
        }

        m.set("task", task)

        lgr.info("proc-def key: ${pinst.processDefinitionKey}; task-name: ${task.name}")
        when (pinst.processDefinitionKey) {
            PROC_SETUP_METER -> when (task.name) {
                TASK_LOC_PULSE -> {
                    return BURL_LOC_PULSE + task.id
                }

                TASK_HISTORY_PULSE -> {
                    return BURL_HISTORY_PULSE + task.id
                }
            }
        }

        m.set("errors", listOf("暂不支持的任务类型: ${task.name}"))

        return ERROR_LIST
    }

    @GetMapping("/launch/{meterId}")
    fun launchMeter(m: Model, @PathVariable("meterId") _meterId: String?): String? {
        val me = DemoApplicationConfiguration.myself()
        me ?: return URL_LOGIN

        var pd: ProcessDefinition? = null
        try {
            val pdList = processRuntime?.processDefinitions(
                    Pageable.of(0, 20),
                    GetProcessDefinitionsPayloadBuilder().withProcessDefinitionKey(PROC_SETUP_METER).build())?.content
            pdList?.sortByDescending { it.version }
            pd = pdList?.firstOrNull()

            if (pd?.key != PROC_SETUP_METER) {
                m.set(ERRORS_KEY, listOf("未发现对应的流程模板: ${PROC_SETUP_METER}."))
                return ERROR_LIST
            }

            val meter = meterMapper!!.selectMeter(MeterParam(firmId = me.firmId + "%", meterId = _meterId)).firstOrNull()
            if (meter == null) {
                m.set(ERRORS_KEY, arrayOf("未找到对应的水表档案: ${_meterId}"))
                return ERROR_LIST
            }

            m.set("pd", pd)
            m.set("firmList", configMapper!!.selectFirm(firmId = me.firmId + "%"))
            m.set("finderList", DemoApplicationConfiguration.myColleges().orEmpty())
            m.set("meter", meter)

            return "meter-list-install"
        } catch (ex: Exception) {
            lgr.error("fail to fetch proc-def with ${PROC_SETUP_METER} caused by ${ex.message}")
            m.set(ERRORS_KEY, listOf("发起流程 ${PROC_SETUP_METER} 故障: ${ex.message}"))
            return ERROR_LIST
        }

    }

    /**
     * 启动流程并完成分配任务。
     */
    @RequestMapping(value = ["/commitBase/{pdId}"], method = [RequestMethod.POST])
    fun commitMeter(m: Model,
                    @PathVariable("pdId") pdId: String,
                    @RequestParam(VAR_CAN_COMMIT, required = false) _canCommit: String?,
                    @RequestParam("meterList") meterList: String,
                    @RequestParam(VAR_METER_ID) _meterId: String,
                    @RequestParam(VAR_METER_NAME) _meterName: String,
                    @RequestParam(VAR_METER_CODE) _meterCode: String,
                    @RequestParam(VAR_USER_CODE) _userCode: String,
                    @RequestParam(VAR_RTU_INSTALL_DATE) _rtuInstallDate: String,
                    @RequestParam(VAR_METER_SIZE) _meterSize: String,
                    @RequestParam(VAR_EXT_ID) _extId: String,
                    @RequestParam(VAR_METER_PULSE) _meterPulse: String,
                    @RequestParam(VAR_METER_LOC) _meterLoc: String,
                    @RequestParam(VAR_FINDER_ID, required = false) _finderId: String?,
                    @RequestParam(VAR_FIRM_ID) _firmId: String): String {
        val me = DemoApplicationConfiguration.myself()
        me ?: return URL_LOGIN

        val user = SecurityContextHolder.getContext().authentication.principal as UserDetails
        // the value will be set to activiti::initiator, here is applyUserId.
        // then it will be used in the following tasks.
        Authentication.setAuthenticatedUserId(user.username)

        if (!_rtuInstallDate.isNullOrBlank() && null == JsonHelper.parseLocalDate(_rtuInstallDate)) {
            m.set(ERRORS_KEY, arrayOf("无效的水表档案: ${_rtuInstallDate}"))
            return ERROR_LIST
        }

        var pd: ProcessDefinition? = null
        try {
            pd = processRuntime?.processDefinition(pdId)

            if (pd?.key != PROC_SETUP_METER) {
                m.set(ERRORS_KEY, listOf("未发现对应的流程模板: ${pdId}."))
                return ERROR_LIST
            }

            // verify input
            lgr.info("got meter: ${_meterId} @ ${_meterLoc}")
            if (_canCommit.isNullOrBlank()
                    || meterList.isNotBlank()
                    || _meterId.isBlank()
                    || _meterName.isBlank()
                    || _rtuInstallDate.isBlank()
                    || _finderId.isNullOrBlank()
                    || _extId.isBlank()) {
                m.set("pd", pd)
                m.set("meterList", meterMapper!!.selectMeter(MeterParam(firmId = me.firmId)))
                m.set("firmList", configMapper!!.selectFirm(firmId = me.firmId + "%"))
                m.set("finderList", DemoApplicationConfiguration.myColleges().orEmpty())
                m.set("meter", ZoneMeter().also {
                    it.id = _meterId
                    it.name = _meterName
                    it.extId = _extId
                    it.rtuInstallDate = JsonHelper.parseLocalDate(_rtuInstallDate)
                    it.size = _meterSize
                    it.meterPulse = _meterPulse.replace(",", "").toDoubleOrNull()
                    it.meterLoc = _meterLoc
                    it.userCode = _userCode
                    it.meterCode = _meterCode
                })

                val meter = meterMapper!!.selectMeter(MeterParam(firmId = me.firmId + "%", meterId = meterList)).firstOrNull()
                if (meter != null) {
                    if (meter.rtuInstallDate == null) {
                        meter.rtuInstallDate = Date()
                    }

                    m.set("meter", meter)
                }

                return "meter-list-install"
            }

            val meter = meterMapper!!.selectMeter(MeterParam(firmId = me.firmId + "%", extId = _extId)).firstOrNull()

            // verify point
            var locStr: String? = null
            try {
                val loc = WKTReader().read(_meterLoc) as Point
                locStr = WKTWriter().write(loc)
            } catch (ex: Exception) {
                lgr.error("Invalid point wkt: ${_meterLoc}: ${ex.message}")
            }

            // start ticket.
            val msg = "大表定位及设定脉冲比-${_meterName}(${me.firmName}): ${user.username}于${DateTime.now().toString(ISODateTimeFormat.dateHourMinute())}发起."
            val pi = processRuntime?.start(StartProcessPayloadBuilder()
                    .withProcessDefinitionId(pdId)
                    .withName(msg)
                    .withBusinessKey(_meterId)
                    .withVariable(VAR_FIRM_ID, _firmId)
                    .withVariable(VAR_METER_ID, _meterId)
                    .withVariable(VAR_METER_NAME, _meterName)
                    .withVariable(VAR_METER_SIZE, _meterSize)
                    .withVariable(VAR_EXT_ID, _extId)
                    .withVariable(VAR_METER_PULSE, _meterPulse)
                    .withVariable(VAR_METER_LOC, locStr)
                    .withVariable(VAR_FINDER_ID, _finderId)
                    .build())

            // create ticket.
            val sm = SetupMeter(pinstId = pi!!.id, meterId = _meterId, firmId = _firmId,
                    meterName = _meterName, meterCode = _meterCode, userCode = _userCode,
                    meterSize = _meterSize,
                    extId = _extId, meterLoc = locStr,
                    meterPulse = _meterPulse.replace(",", "").toDoubleOrNull(),
                    oldName = meter?.name, oldCode = meter?.meterCode, oldSize = meter?.size,
                    oldExtId = meter?.extId, oldLoc = meter?.meterLoc, oldPulse = meter?.meterPulse).apply {
                rtuInstallDate = JsonHelper.parseLocalDate(_rtuInstallDate)

                initiator = user.username
                initTime = Date()

                finderId = _finderId
            }
            ticketMapper!!.createSetupMeter(sm)

            // start event.
            kotlin.run {
                val tlist = taskRuntime?.tasks(Pageable.of(0, 5),
                        GetTasksPayloadBuilder().withProcessInstanceId(pi.id).build())
                val t1 = tlist?.content?.lastOrNull()
                if (t1 != null) {
                    val t2 = taskRuntime?.complete(CompleteTaskPayloadBuilder()
                            .withTaskId(t1.id)
                            .withVariable(VAR_FIRM_ID, me.firmId)
                            .withVariable(VAR_METER_ID, _meterId)
                            .withVariable(VAR_METER_NAME, _meterName)
                            .withVariable(VAR_METER_SIZE, _meterSize)
                            .withVariable(VAR_EXT_ID, _extId)
                            .withVariable(VAR_METER_PULSE, _meterPulse)
                            .withVariable(VAR_METER_LOC, locStr)
                            .withVariable(VAR_FINDER_ID, _finderId)
                            .build())
                }
            }

            return RM_TASK_LIST
        } catch (ex: Exception) {
            lgr.error("fail to fetch proc-def with ${pdId} caused by ${ex.message}")
            m.set(ERRORS_KEY, listOf("分配流程 ${pdId} 故障: ${ex.message}"))
            return ERROR_LIST
        }
    }

    /**
     * 取得水表信息
     */
    @RequestMapping(value = ["/locPulse/{taskId}"], method = [RequestMethod.GET])
    fun claimLocPulse(m: Model,
                      @PathVariable("taskId") taskId: String): String {
        val me = DemoApplicationConfiguration.myself()
        me ?: return URL_LOGIN

        var task: Task? = null
        try {
            task = taskRuntime!!.task(taskId)

            val pi = processRuntime!!.processInstance(task!!.processInstanceId)
            val vs = taskRuntime!!.variables(GetTaskVariablesPayload(taskId))

            // find in data.
            val sm = ticketMapper!!.selectSetupMeter(SetupMeter(pinstId = pi!!.id))

            m.set("task", task)
            m.set("meter", sm!!)

            return "meter-loc-pulse"
        } catch (ex: Exception) {
            m.set(ERRORS_KEY, arrayOf("签收任务 ${taskId} 故障: ${ex.message}"))

            return ERROR_LIST
        }
    }

    /**
     * 删除流程实例
     */
    @RequestMapping("/deleteInst/{pinstId}")
    fun deleteInst(m: Model, @PathVariable("pinstId") _pinstId: String): String {
        val me = DemoApplicationConfiguration.myself()
        me ?: return URL_LOGIN
        if (me.roles?.find { it.name == ROLE_ADMIN } == null) {
            m.set("errors", listOf("您无权中断流程，请跟水司领导联系."))
            return "/lossMng/taskList"
        }

        val pinst = processRuntime?.processInstance(_pinstId)

        var ticket: SetupMeter? = null
        if (true == pinst?.processDefinitionKey?.startsWith(PROC_SETUP_METER)) {
            ticket = ticketMapper?.selectSetupMeter(SetupMeter(pinstId = pinst.id))
        }

        if (ticket != null) {
            m.set("tp", TaskProcInfo(null, pinst))
            return VIEW_CANCEL_LOSS
        }

        // 工单不存在，直接删除流程
        if (pinst != null && pinst.status != ProcessInstance.ProcessInstanceStatus.DELETED) {
            processRuntime?.delete(DeleteProcessPayloadBuilder().withProcessInstanceId(_pinstId).build())

            m.set("errors", arrayOf("删除正在进行的流程: ${pinst.name}"))
        }

        return "/lossMng/pinstList"
    }

    /**
     * 签收任务，根据任务名称定向到不同的视图
     */
    @RequestMapping("/cancelTask/{taskId}")
    fun cancelTask(m: Model, @PathVariable("taskId") taskId: String, req: HttpServletRequest): String {
        val me = DemoApplicationConfiguration.myself()
        me ?: return URL_LOGIN
        if (me.roles?.find { it.name == ROLE_ADMIN } == null) {
            m.set("errors", listOf("您无权中断任务，请跟水司领导联系."))
            return "/lossMng/taskList"
        }

        val task: Task?
        try {
            task = taskRuntime?.task(taskId)
        } catch (ex: Exception) {
            lgr.error("fail to fetch task ${taskId} caused by ${ex.message}")
            m.set(ERRORS_KEY, listOf("任务已完成(或不存在): ${taskId}"))
            return RM_TASK_LIST
        }

        val pinst = processRuntime?.processInstance(task?.processInstanceId)

        var ticket: SetupMeter? = null
        if (true == pinst?.processDefinitionKey?.startsWith(PROC_SETUP_METER)) {
            ticket = ticketMapper?.selectSetupMeter(SetupMeter(pinstId = pinst.id))
        }

        if (ticket != null) {
            // 工单存在，则填写取消原因
            m.set("tp", TaskProcInfo(task, pinst))
            return VIEW_CANCEL_LOSS
        } else {
            // 工单不存在，直接取消
            processRuntime?.delete(DeleteProcessPayloadBuilder().withProcessInstance(pinst).build())
            lgr.info("cancelled task ${taskId} and its process instance ${pinst!!.id}: ${pinst.name}")
        }

        return RM_TASK_LIST
    }

    /**
     * 取消工单
     */
    @RequestMapping("cancelTicket/{pinstId}")
    fun cancelTicket(m: Model, @PathVariable("pinstId") _pinstId: String,
                     @RequestParam("cancelReason") _cancelReason: String?): String {
        val me = DemoApplicationConfiguration.myself()
        me ?: return URL_LOGIN
        if (me.roles?.find { it.name == ROLE_ADMIN } == null) {
            m.set("errors", listOf("您无权中断流程，请跟水司领导联系."))
            return "/lossMng/taskList"
        }

        val pinst = processRuntime!!.processInstance(_pinstId)
        if (_cancelReason.isNullOrBlank()) {
            m.set(ERRORS_KEY, arrayOf("请填写终止原因"))

            m.set("tp", TaskProcInfo(null, pinst))
            return VIEW_CANCEL_LOSS
        }

        processRuntime!!.delete(DeleteProcessPayloadBuilder().withProcessInstanceId(_pinstId)
                .withReason(_cancelReason).build())

        var ticket: SetupMeter? = null
        if (true == pinst?.processDefinitionKey?.startsWith(PROC_SETUP_METER)) {
            val cnt = ticketMapper?.updateSetupMeter(SetupMeter().apply {
                pinstId = _pinstId
                cancelReason = _cancelReason
                cancelTime = Date()
                canceller = me.id
            })

            lgr.info("cancel ticket ${_pinstId} count: ${cnt}")
        }

        return RM_TASK_LIST
    }

    /**
     * 提交水表档案。
     */
    @RequestMapping(value = ["/commitLocPulse/{taskId}"], method = [RequestMethod.POST])
    fun commitLocPulse(m: Model,
                       @PathVariable("taskId") taskId: String,
                       @RequestParam(VAR_METER_SIZE) _meterSize: String,
                       @RequestParam(VAR_EXT_ID) _extId: String,
                       @RequestParam(VAR_METER_PULSE) _meterPulse: String,
                       @RequestParam(VAR_METER_LOC) _meterLoc: String): String {
        val me = DemoApplicationConfiguration.myself()
        me ?: return URL_LOGIN

        var task: Task? = null
        try {
            task = taskRuntime!!.task(taskId)

            val pi = processRuntime!!.processInstance(task!!.processInstanceId)
            val vs = taskRuntime!!.variables(GetTaskVariablesPayload(taskId))

            // verify point
            var locStr: String? = null
            try {
                // wkt: GEOMETRYCOLLECTION(POINT(39.984335 116.310097))
                val loc = WKTReader().read(_meterLoc) as GeometryCollection
                locStr = WKTWriter().write(loc.getGeometryN(0) as Point)
            } catch (ex: Exception) {
                lgr.error("Invalid geometry-collection<point> wkt: ${_meterLoc}: ${ex.message}")
            }

            // find setup-meter
            val sm = ticketMapper!!.selectSetupMeter(SetupMeter(pinstId = pi!!.id))?.also {
                it.meterSize = _meterSize
                it.extId = _extId
                it.meterPulse = _meterPulse.replace(",", "").toDoubleOrNull()
                it.meterLoc = locStr

                it.finderId = me.id
                it.findTime = Date()

                ticketMapper!!.updateSetupMeter(it)

                // commit task.
                taskRuntime!!.complete(CompleteTaskPayloadBuilder().withTaskId(taskId)
                        .withVariable(VAR_METER_ID, it.meterId)
                        .withVariable(VAR_FIRM_ID, it.firmId)
                        .build())
            }

            return RM_TASK_LIST
        } catch (ex: Exception) {
            m.set(ERRORS_KEY, arrayOf("提交任务 ${taskId} 错误: ${ex.message}"))

            return ERROR_LIST
        }
    }

    @RequestMapping(value = ["/historyPulse/{taskId}"], method = [RequestMethod.GET])
    fun meterHistoryPulse(m: Model,
                          @PathVariable("taskId") taskId: String): String {
        val me = DemoApplicationConfiguration.myself()
        me ?: return URL_LOGIN

        var task: Task? = null
        try {
            task = taskRuntime!!.task(taskId)

            val pi = processRuntime!!.processInstance(task!!.processInstanceId)
            val vs = taskRuntime!!.variables(GetTaskVariablesPayload(taskId))

            // find in data.
            val sm = ticketMapper!!.selectSetupMeter(SetupMeter(pinstId = pi!!.id))
            val pulseList = ticketMapper!!.meterHistoryPulse(MeterHistoryPulse(meterId = sm!!.extId).also {
                it.startTime = sm.rtuInstallDate
            })

            m.set("task", task)
            m.set("meter", sm)
            m.set("pulseList", pulseList.orEmpty())

            return "meter-history-pulse"
        } catch (ex: Exception) {
            m.set(ERRORS_KEY, arrayOf("签收任务 ${taskId} 错误: ${ex.message}"))

            return ERROR_LIST
        }
    }

    /**
     * 修改历史数据的脉冲比。
     */
    @RequestMapping(value = ["/commitHistoryPulse/{taskId}"], method = [RequestMethod.POST])
    fun commitHistoryPulse(m: Model,
                           @PathVariable("taskId") taskId: String,
                           @RequestParam(VAR_CHANGE_CONFIRM, required = false) _changeConfirm: String?,
                           @RequestParam(VAR_CHANGE_LOC_CONFIRM, required = false) _changeLocConfirm: String?,
                           @RequestParam(VAR_METER_SIZE) _meterSize: String,
                           @RequestParam(VAR_EXT_ID) _extId: String,
                           @RequestParam(VAR_METER_PULSE) _meterPulse: String,
                           @RequestParam(VAR_METER_LOC) _meterLoc: String,
                           @RequestParam(VAR_CREATE_SAME_DMA, required = false) _createDma: String?): String {
        val me = DemoApplicationConfiguration.myself()
        me ?: return URL_LOGIN

        val errlist = ArrayList<String>()
        var task: Task? = null
        try {
            task = taskRuntime!!.task(taskId)

            val pi = processRuntime!!.processInstance(task!!.processInstanceId)
            val vs = taskRuntime!!.variables(GetTaskVariablesPayload(taskId))

            val meter = ZoneMeter().also { meter ->
                for (v1 in vs) {
                    when (v1.name) {
                        VAR_METER_ID -> meter.id = v1.getValue()
                        VAR_METER_NAME -> meter.name = v1.getValue()
                        VAR_FIRM_ID -> meter.firmId = v1.getValue()
                        VAR_METER_CODE -> meter.meterCode = v1.getValue()
                    }
                }
            }

            // verify point: wkt: POINT(39.984335 116.310097)
            var locStr: String? = null
            if (!_changeLocConfirm.isNullOrBlank()) {
                try {
                    val loc = WKTReader().read(_meterLoc) as Geometry
                    locStr = WKTWriter().write(loc as Point)
                } catch (ex: Exception) {
                    lgr.error("Invalid geometry-collection<point> wkt: ${_meterLoc}: ${ex.message}")
                }
            }

            // find setup-meter
            val sm = ticketMapper!!.selectSetupMeter(SetupMeter(pinstId = pi!!.id, meterId = meter.id))?.also {
                it.meterSize = _meterSize
                it.extId = _extId
                it.meterPulse = _meterPulse.replace(",", "").toDoubleOrNull()
                it.meterLoc = locStr

                // 更新水表档案
                if (_changeConfirm.isNullOrBlank()) {
                    // 更新工单
                    ticketMapper!!.updateSetupMeter(it)
                } else {
                    // 更新档案
                    val m1 = ZoneMeter().apply {
                        id = null
                        name = it.meterName

                        // firm-id can't be changed 'cause it's firm-id may be set by the group account.
//                        firmId = it.firmId
                        meterCode = it.meterCode
                        userCode = it.userCode
                        size = it.meterSize
                        extId = it.extId
                        meterPulse = it.meterPulse
                        meterLoc = it.meterLoc
                        rtuInstallDate = it.rtuInstallDate

                        typeId = BwMeterType.CHECK.name

                        createBy = me.id
                        createDate = Date()
                        updateBy = me.id
                        updateDate = Date()
                    }

                    // change all meters with 'ext-id'.
                    if (1 > meterMapper?.updateMeterByValue(m1) ?: 0) {
                        m1.id = it.meterId

                        meterMapper?.insertMeter(m1)
                    }

                    // 查看历史数据的脉冲比
                    val pulseList = ticketMapper!!.meterHistoryPulse(MeterHistoryPulse(meterId = it.extId).apply {
                        startTime = it.rtuInstallDate
                    })

                    it.oldCount1 = pulseList?.firstOrNull()?.oldCount ?: 0
                    it.startTime1 = pulseList?.firstOrNull()?.startTime
                    it.endTime1 = pulseList?.firstOrNull()?.endTime

                    // 更新历史数据的脉冲比及工单.
                    it.changeCount = ticketMapper!!.updateHistoryPulse(MeterHistoryPulse(
                            meterId = it.extId,
                            meterPulse = it.meterPulse).apply {
                        startTime = it.rtuInstallDate
                    })
                    it.changeTime = Date()
                    it.totalCount = pulseList?.sumBy { p1 -> p1.oldCount ?: 0 }

                    // 更新工单
                    ticketMapper!!.updateSetupMeter(it)

                    errlist.add("更新 ${it.extId} 历史数据的脉冲比为 ${it.meterPulse} 计 ${it.changeCount} 行.")
                }
            }

            if (!_createDma.isNullOrBlank()) {
                // 创建同名、同位置DMA
                if (meterMapper!!.selectDma(sm!!.firmId + "%", sm.meterId, null).isEmpty()) {
                    // select meter firstly.
                    val m1 = meterMapper!!.selectMeter(MeterParam(meterId = sm.meterId)).firstOrNull()

                    val dma = BwDma().apply {
                        id = sm.meterId
                        name = sm.meterName

                        // current account may be group account. dma.firm-id should be same as meter.
                        firmId = m1?.firmId ?: sm.firmId

                        r = 7.0
                        dmaType = DmaType.RESIDENT.name
                        showType = ShowType.LOSS.name
                        status = MeterStatus.WORK.name

                        createBy = me.id
                        createDate = Date()
                        updateBy = me.id
                        updateDate = Date()
                    }

                    meterMapper!!.insertDma(dma)

                    // 位置
                    val dl = BwDmaLoc().apply {
                        dmaId = dma.id
                        dmaLoc = locStr

                        createBy = me.id
                        createDate = Date()
                        updateBy = me.id
                        updateDate = Date()
                    }

                    if (!dl.dmaLoc.isNullOrBlank()) {
                        if (locMapper!!.updateDmaLoc(dl) < 1) {
                            locMapper!!.insertDmaLoc(dl)
                        }
                    }

                    // 关联
                    val m2 = ZoneMeter().apply {
                        id = sm.meterId
                        dmaId = dma.id
                    }

                    meterMapper!!.updateMeterByValue(m2)

                    errlist.add("已创建同名DMA分区: ${sm.meterName} (${sm.meterId})")
                } else {
                    errlist.add("同名DMA分区已存在: ${sm.meterName} (${sm.meterId})")
                }
            }

            m.set(ERRORS_KEY, errlist)

            taskRuntime!!.complete(CompleteTaskPayloadBuilder().withTaskId(taskId)
                    .withVariable(VAR_METER_ID, sm!!.meterId)
                    .withVariable(VAR_FIRM_ID, sm.firmId)
                    .build())

            return RM_TASK_LIST
        } catch (ex: Exception) {
            m.set(ERRORS_KEY, arrayOf("提交任务 ${taskId} 错误: ${ex.message}"))

            return ERROR_LIST
        }
    }

    /**
     * 显示进度图
     */
    @RequestMapping("/currentDiagram/{pinstId}")
    fun currentDiagram(m: Model, @PathVariable("pinstId") pinstId: String): String {
        val me = DemoApplicationConfiguration.myself()
        me ?: return LossMngController.URL_LOGIN

        val pd = processRuntime?.processInstance(pinstId)
        val aids = processRuntime?.processInstanceMeta(pinstId)?.activeActivitiesIds
        lgr.info("active activity id list: ${aids?.joinToString()}")

        val bmodel = processEngine?.repositoryService?.getBpmnModel(pd?.processDefinitionId)

        val giList = bmodel?.locationMap?.filterKeys { gk ->
            aids?.contains(gk) == true
        }?.values
        m.set("giList", giList!!)

        return "setup-meter-diagram"
    }

    companion object {
        private val lgr = LoggerFactory.getLogger(SetupMeterController::class.java)

        const val PROC_SETUP_METER = "procSetupMeter"
        const val TASK_LOC_PULSE = "定位及脉冲"
        const val TASK_HISTORY_PULSE = "历史脉冲调整"
        const val BURL_LOC_PULSE = "/setupMeter/locPulse/"
        const val BURL_HISTORY_PULSE = "/setupMeter/historyPulse/"

        const val VIEW_CANCEL_LOSS = "cancel-setup-meter"

        const val ERRORS_KEY = "errors"
        const val ERROR_LIST = "error-list"

        const val RM_TASK_LIST = "/lossMng/taskList"

        const val VAR_FIRM_ID = "firmId"
        const val VAR_METER_ID = "meterId"
        const val VAR_METER_CODE = "meterCode"
        const val VAR_USER_CODE = "userCode"
        const val VAR_METER_NAME = "meterName"
        const val VAR_RTU_INSTALL_DATE = "rtuInstallDate"
        const val VAR_METER_SIZE = "meterSize"
        const val VAR_EXT_ID = "extId"
        const val VAR_METER_PULSE = "meterPulse"
        const val VAR_METER_LOC = "meterLoc"
        const val VAR_FINDER_ID = "finderId"

        const val VAR_CAN_COMMIT = "canCommit"
        const val VAR_CHANGE_CONFIRM = "changeConfirm"
        const val VAR_CHANGE_LOC_CONFIRM = "changeLocConfirm"
        const val VAR_CREATE_SAME_DMA = "createSameDma"

        /**
         * 工单有效期
         */
        const val DAYS_EXPIRE: Int = 30

        const val URL_LOGIN = "redirect:/login"

        const val ROLE_ADMIN = "ADMINISTRATOR"

        const val LOGIN_ADMIN = "admin"
    }
}